/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
*
* This package is free software;  you can redistribute it and/or
* modify it under the terms of the license found in the file
* named COPYING that should have accompanied this file.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

// interface header
#include "ServerLink.h"
// #include "MsgStrings.h"

#if defined(DEBUG)
#define NETWORK_STATS
#endif

// system headers
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <time.h>
#include <vector>
#if !defined(_WIN32)
#include <unistd.h>
#include <errno.h>
#endif
#include <stdio.h>
#include <stdlib.h>

// common implementation headers
#include "ErrorHandler.h"
// invoke persistent rebuilding for current version dates
#include "version.h"
#if defined(NETWORK_STATS)
#include "bzfio.h"
#endif
#include "TimeKeeper.h"

#define UDEBUG if (UDEBUGMSG) printf
#define UDEBUGMSG false

#if defined(NETWORK_STATS)
static TimeKeeper   startTime;
static uint32_t     bytesSent;
static uint32_t     bytesReceived;
static uint32_t     packetsSent;
static uint32_t     packetsReceived;
#endif

#if defined(_WIN32)
DWORD ThreadID;     // Thread ID
HANDLE hConnected;  // "Connected" event
HANDLE hThread;     // Connection thread

typedef struct
{
    int query;
    CNCTType* addr;
    int saddr;
} TConnect;

TConnect conn;

DWORD WINAPI ThreadConnect(LPVOID params)
{
    TConnect *conn = (TConnect*)params;
    if (connect(conn->query, conn->addr, conn->saddr) >= 0)
    {
        SetEvent(hConnected); // Connect successful
    }
    ExitThread(0);
    return 0;
}

#endif // !defined(_WIN32)

// FIXME -- packet recording
FILE* packetStream = NULL;
TimeKeeper packetStartTime;
static const unsigned long serverPacket = 1;
static const unsigned long endPacket = 0;

ServerLink*     ServerLink::server = NULL;

ServerLink::ServerLink(const Address& serverAddress, int port)
{
    int i;

    struct protoent* p;
#if defined(_WIN32)
    BOOL off = FALSE;
#else
    int off = 0;
#endif

    // standard server has no special abilities;
    server_abilities = Nothing;

    // queue is empty

    urecvfd = -1;

    ulinkup = false;

    // initialize version to a bogus number
    strcpy(version, "BZFS0000");

    // open connection to server.  first connect to given port.
    // don't wait too long.
    int query = socket(AF_INET, SOCK_STREAM, 0);
    if (query < 0) return;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr = serverAddress;

    UDEBUG("Remote %s\n", inet_ntoa(addr.sin_addr));

    // for UDP, used later
    memcpy((unsigned char *)&usendaddr, (unsigned char *)&addr, sizeof(addr));

    bool okay = true;
    int fdMax = query;
    struct timeval timeout;
    fd_set write_set;
    fd_set read_set;
    int nfound;

#if !defined(_WIN32)
    okay = true;
    fdMax = query;
    if (BzfNetwork::setNonBlocking(query) < 0)
    {
        close(query);
        return;
    }
    if (connect(query, (CNCTType*)&addr, sizeof(addr)) < 0)
    {
        if (getErrno() != EINPROGRESS)
        {
            close(query);
            return;
        }
        FD_ZERO(&write_set);
        FD_SET((unsigned int)query, &write_set);
        timeout.tv_sec = long(5);
        timeout.tv_usec = 0;
        nfound = select(fdMax + 1, NULL, (fd_set*)&write_set, NULL, &timeout);
        if (nfound <= 0)
        {
            close(query);
            return;
        }
        int       connectError;
        socklen_t errorLen = sizeof(int);
        if (getsockopt(query, SOL_SOCKET, SO_ERROR, &connectError, &errorLen)
                < 0)
        {
            close(query);
            return;
        }
        if (connectError != 0)
        {
            close(query);
            return;
        }
    }
#else // Connection timeout for Windows

    // Initialize structure
    conn.query = query;
    conn.addr = (CNCTType*)&addr;
    conn.saddr = sizeof(addr);

    // Create event
    hConnected = CreateEvent(NULL, FALSE, FALSE, "Connected Event");

    hThread = CreateThread(NULL, 0, ThreadConnect, &conn, 0, &ThreadID);
    okay = (WaitForSingleObject(hConnected, 5000) == WAIT_OBJECT_0);
    if (!okay)
        TerminateThread(hThread, 1);

    // Do some cleanup
    CloseHandle(hConnected);
    CloseHandle(hThread);

#endif // !defined(_WIN32)
    if (!okay)
    {
        close(query);
        return;
    }

    // send out the connect header
    // this will let the server know we are BZFS protocol.
    // after the server gets this it will send back a version for us to check
    int sendRepply = ::send(query, BZ_CONNECT_HEADER, (int)strlen(BZ_CONNECT_HEADER), 0);

    logDebugMessage(2, "CONNECT:send in connect returned %d\n", sendRepply);

    // wait to get data back. we are still blocking so these
    // calls should be sync.

    FD_ZERO(&read_set);
    FD_ZERO(&write_set);
    FD_SET((unsigned int)query, &read_set);
    FD_SET((unsigned int)query, &write_set);

    timeout.tv_sec = long(10);
    timeout.tv_usec = 0;

    // pick some limit to time out on ( in seconds )
    double thisStartTime = TimeKeeper::getCurrent().getSeconds();
    double connectTimeout = 30.0;
    if (BZDB.isSet("connectionTimeout"))
        connectTimeout = BZDB.eval("connectionTimeout");

    bool gotNetData = false;

    // loop calling select untill we read some data back.
    // its only 8 bytes so it better come back in one packet.
    int loopCount = 0;
    while (!gotNetData)
    {
        loopCount++;
        nfound = select(fdMax + 1, (fd_set*)&read_set, (fd_set*)&write_set, NULL, &timeout);

        // there has to be at least one socket active, or we are screwed
        if (nfound <= 0)
        {
            logDebugMessage(1, "CONNECT:select in connect failed, nfound = %d\n", nfound);
            close(query);
            return;
        }

        // try and get data back from the server
        i = recv(query, (char*)version, 8, 0);

        // if we got some, then we are done
        if (i > 0)
        {
            logDebugMessage(2, "CONNECT:got net data in connect, bytes read = %d\n", i);
            logDebugMessage(2, "CONNECT:Time To Connect = %f\n", (TimeKeeper::getCurrent().getSeconds() - thisStartTime));
            gotNetData = true;
        }
        else
        {
            // if we have waited too long, then bail
            if ((TimeKeeper::getCurrent().getSeconds() - thisStartTime) > connectTimeout)
            {
                logDebugMessage(1, "CONNECT:connect time out failed\n");
                logDebugMessage(2, "CONNECT:connect loop count = %d\n", loopCount);
                close(query);
                return;
            }

            TimeKeeper::sleep(0.25f);
        }
    }

    logDebugMessage(2, "CONNECT:connect loop count = %d\n", loopCount);

    // if we got back less than the expected connect response (BZFSXXXX)
    // then something went bad, and we are done.
    if (i < 8)
    {
        close(query);
        return;
    }

    // since we are connected, we can go non blocking
    // on BSD sockets systems
    // all other messages after this are handled via the normal
    // message system
#if !defined(_WIN32)
    if (BzfNetwork::setNonBlocking(query) < 0)
    {
        close(query);
        return;
    }
#endif

    // FIXME is it ok to try UDP always?
    server_abilities |= CanDoUDP;
    if (strcmp(version, getServerVersion()) != 0)
    {
        state = BadVersion;

        if (strcmp(version, BanRefusalString) == 0)
        {
            state = Refused;
            char message[512];
            int len = recv(query, (char*)message, 512, 0);
            if (len > 0)
                message[len - 1] = 0;
            else
                message[0] = 0;
            rejectionMessage = message;
        }

        close(query);
        return;
    }

    // read local player's id
#if !defined(_WIN32)
    FD_ZERO(&read_set);
    FD_SET((unsigned int)query, &read_set);
    timeout.tv_sec = long(5);
    timeout.tv_usec = 0;
    nfound = select(fdMax + 1, (fd_set*)&read_set, NULL, NULL, &timeout);
    if (nfound <= 0)
    {
        close(query);
        return;
    }
#endif // !defined(_WIN32)
    i = recv(query, (char *)&id, sizeof(id), 0);
    if (i < (int) sizeof(id))
        return;
    if (id == 0xff)
    {
        state = Rejected;
        close(query);
        return;
    }

#if !defined(_WIN32)
    if (BzfNetwork::setBlocking(query) < 0)
    {
        close(query);
        return;
    }
#endif // !defined(_WIN32)

    fd = query;

    // turn on TCP no delay
    p = getprotobyname("tcp");
    if (p)
        setsockopt(fd, p->p_proto, TCP_NODELAY, (SSOType)&off, sizeof(off));  // changed

    state = Okay;
#if defined(NETWORK_STATS)
    bytesSent = 0;
    bytesReceived = 0;
    packetsSent = 0;
    packetsReceived = 0;
#endif

    // FIXME -- packet recording
    if (getenv("BZFLAGSAVE"))
    {
        packetStream = fopen(getenv("BZFLAGSAVE"), "w");
        packetStartTime = TimeKeeper::getCurrent();
    }
    return;

}

ServerLink::~ServerLink()
{
    if (state != Okay) return;
    shutdown(fd, 2);
    close(fd);

    if (urecvfd >= 0)
        close(urecvfd);

    urecvfd = -1;
    ulinkup = false;

    // FIXME -- packet recording
    if (packetStream)
    {
        long dt = (long)((TimeKeeper::getCurrent() - packetStartTime) * 10000.0f);
        size_t items_written = fwrite(&endPacket, sizeof(endPacket), 1, packetStream);
        if (items_written == 1)
            items_written = fwrite(&dt, sizeof(dt), 1, packetStream);
        if (items_written != 1)
            printError("Error writing on packetStream");
        fclose(packetStream);
    }

#if defined(NETWORK_STATS)
    const float dt = float(TimeKeeper::getCurrent() - startTime);
    logDebugMessage(1, "Server network statistics:\n");
    logDebugMessage(1, "  elapsed time    : %f\n", dt);
    logDebugMessage(1, "  bytes sent      : %d (%f/sec)\n", bytesSent, (float)bytesSent / dt);
    logDebugMessage(1, "  packets sent    : %d (%f/sec)\n", packetsSent, (float)packetsSent / dt);
    if (packetsSent != 0)
        logDebugMessage(1, "  bytes/packet    : %f\n", (float)bytesSent / (float)packetsSent);
    logDebugMessage(1, "  bytes recieved  : %d (%f/sec)\n", bytesReceived, (float)bytesReceived / dt);
    logDebugMessage(1, "  packets received: %d (%f/sec)\n", packetsReceived, (float)packetsReceived / dt);
    if (packetsReceived != 0)
        logDebugMessage(1, "  bytes/packet    : %f\n", (float)bytesReceived / (float)packetsReceived);
#endif
}

ServerLink* ServerLink::getServer() // const
{
    return server;
}

void ServerLink::setServer(ServerLink* _server)
{
    server = _server;
}

void ServerLink::send(uint16_t code, uint16_t len, const void* msg)
{
    bool needForSpeed = false;
    if (state != Okay) return;
    //  if (code != MsgPlayerUpdateSmall && code != MsgPlayerUpdate)
    //    logDebugMessage(1,"send %s len %d\n",MsgStrings::strMsgCode(code),len);
    char msgbuf[MaxPacketLen];
    void* buf = msgbuf;
    buf = nboPackUShort(buf, len);
    buf = nboPackUShort(buf, code);
    if (msg && len != 0) buf = nboPackString(buf, msg, len);

    if ((urecvfd >= 0) && ulinkup)
    {
        switch (code)
        {
        case MsgFireShot:
        case MsgShotEnd:
        case MsgPlayerUpdate:
        case MsgPlayerUpdateSmall:
        case MsgGMUpdate:
        case MsgUDPLinkRequest:
        case MsgUDPLinkEstablished:
            needForSpeed = true;
            break;
        }
    }
    // MsgUDPLinkRequest always goes udp
    if (code == MsgUDPLinkRequest)
        needForSpeed = true;

    if (needForSpeed)
    {
#ifdef TESTLINK
        if ((random() % TESTQUALTIY) != 0)
#endif
            sendto(urecvfd, (const char *)msgbuf, (char*)buf - msgbuf, 0, &usendaddr, sizeof(usendaddr));
        // we don't care about errors yet
        return;
    }

    int r = ::send(fd, (const char*)msgbuf, len + 4, 0);
    (void)r; // silence g++
#if defined(_WIN32)
    if (r == SOCKET_ERROR)
    {
        const int e = WSAGetLastError();
        if (e == WSAENETRESET || e == WSAECONNABORTED ||
                e == WSAECONNRESET || e == WSAETIMEDOUT)
            state = Hungup;
        r = 0;
    }
#endif

#if defined(NETWORK_STATS)
    bytesSent += r;
    packetsSent++;
#endif
}

#ifdef WIN32
/* This is a really really fugly hack to get around winsock sillyness
* The newer versions of winsock have a socken_t typedef, and there
* doesn't seem to be any way to tell the versions apart. However,
* VC++ helps us out here by treating typedef as #define
* If we've got a socklen_t typedefed, define HAVE_SOCKLEN_T to
* avoid #define'ing it in common.h */

#ifndef socklen_t
#define socklen_t int
#endif
#endif //WIN32

int ServerLink::read(uint16_t& code, uint16_t& len, void* msg, int blockTime)
{

    code = MsgNull;
    len = 0;

    if (state != Okay) return -1;

    if ((urecvfd >= 0) /* && ulinkup */)
    {

        if (!udpLength)
        {
            AddrLen recvlen = sizeof(urecvaddr);
            int n = recvfrom(urecvfd, ubuf, MaxPacketLen, 0,
                             &urecvaddr, (socklen_t*)&recvlen);
            if (n > 0)
            {
                udpLength = n;
                udpBufferPtr = ubuf;
            }
        }
        if (udpLength)
        {
            // unpack header and get message
            udpLength -= 4;
            if (udpLength < 0)
            {
                udpLength = 0;
                return -1;
            }
            udpBufferPtr = (const char *)nboUnpackUShort(udpBufferPtr, len);
            udpBufferPtr = (const char *)nboUnpackUShort(udpBufferPtr, code);
            //      if (code != MsgPlayerUpdateSmall && code != MsgPlayerUpdate && code != MsgGameTime)
            //  logDebugMessage(1,"rcvd %s len %d\n",MsgStrings::strMsgCode(code),len);
            UDEBUG("<** UDP Packet Code %x Len %x\n", code, len);
            if (len > udpLength)
            {
                udpLength = 0;
                return -1;
            }
            memcpy((char *)msg, udpBufferPtr, len);
            udpBufferPtr += len;
            udpLength -= len;
            return 1;
        }
        if (UDEBUGMSG) printError("Fallback to normal TCP receive");
        len = 0;
        code = MsgNull;

        blockTime = 0;
    }

    // block for specified period.  default is no blocking (polling)
    struct timeval timeout;
    timeout.tv_sec = blockTime / 1000;
    timeout.tv_usec = blockTime - 1000 * timeout.tv_sec;

    // only check server
    fd_set read_set;
    FD_ZERO(&read_set);
    FD_SET((unsigned int)fd, &read_set);
    int nfound = select(fd + 1, (fd_set*)&read_set, NULL, NULL,
                        (struct timeval*)(blockTime >= 0 ? &timeout : NULL));
    if (nfound == 0) return 0;
    if (nfound < 0) return -1;

    // printError("<** TCP Packet Code Received %d", time(0));
    // FIXME -- don't really want to take the chance of waiting forever
    // on the remaining select() calls, but if the server and network
    // haven't been hosed then the data will get here soon.  And if the
    // server or network is down then we don't really care anyway.

    // get packet header -- keep trying until we get 4 bytes or an error
    char headerBuffer[4];


    int rlen = 0;
    rlen = recv(fd, (char*)headerBuffer, 4, 0);

    int tlen = rlen;
    while (rlen >= 1 && tlen < 4)
    {
        printError("ServerLink::read() loop");
        FD_ZERO(&read_set);
        FD_SET((unsigned int)fd, &read_set);
        nfound = select(fd + 1, (fd_set*)&read_set, NULL, NULL, NULL);
        if (nfound == 0) continue;
        if (nfound < 0) return -1;
        rlen = recv(fd, (char*)headerBuffer + tlen, 4 - tlen, 0);
        if (rlen >= 0) tlen += rlen;
    }
    if (tlen < 4) return -1;
#if defined(NETWORK_STATS)
    bytesReceived += 4;
    packetsReceived++;
#endif

    // unpack header and get message
    const void* buf = headerBuffer;
    buf = nboUnpackUShort(buf, len);
    buf = nboUnpackUShort(buf, code);

    //  logDebugMessage(1,"rcvd %s len %d\n",MsgStrings::strMsgCode(code),len);
    if (len > MaxPacketLen - 4)
        return -1;
    if (len > 0)
        rlen = recv(fd, (char*)msg, int(len), 0);
    else
        rlen = 0;
#if defined(NETWORK_STATS)
    if (rlen >= 0) bytesReceived += rlen;
#endif
    if (rlen != int(len))
    {
        // keep reading until we get the whole message
        tlen = rlen;
        while (rlen >= 1 && tlen < int(len))
        {
            FD_ZERO(&read_set);
            FD_SET((unsigned int)fd, &read_set);
            nfound = select(fd + 1, (fd_set*)&read_set, 0, 0, NULL);
            if (nfound == 0) continue;
            if (nfound < 0) return -1;
            rlen = recv(fd, (char*)msg + tlen, int(len) - tlen, 0);
            if (rlen >= 0) tlen += rlen;
#if defined(NETWORK_STATS)
            if (rlen >= 0) bytesReceived += rlen;
#endif
        }
        if (tlen < int(len)) return -1;
    }

    // FIXME -- packet recording
    if (packetStream)
    {
        long dt = (long)((TimeKeeper::getCurrent() - packetStartTime) * 10000.0f);
        size_t items_written = fwrite(&serverPacket, sizeof(serverPacket), 1, packetStream);
        if (items_written == 1)
            items_written = fwrite(&dt, sizeof(dt), 1, packetStream);
        if (items_written == 1)
            items_written = fwrite(headerBuffer, 4, 1, packetStream);
        if (items_written == 1)
            items_written = fwrite(msg, len, 1, packetStream);
        if (items_written != 1)
            printError("Error writing on packetStream");
    }
    return 1;
}

void ServerLink::sendEnter(PlayerType type, TeamColor team, int skinIndex, const char* name, const char* motto,
                           const char* token, const char* locale)
{
    if (state != Okay) return;
    char msg[PlayerIdPLen + 4 + 4 + CallSignLen + MottoLen + TokenLen + VersionLen + LocaleLen];
    ::memset(msg, 0, sizeof(msg));
    void* buf = msg;
    buf = nboPackUShort(buf, uint16_t(type));
    buf = nboPackUShort(buf, uint16_t(team));
    buf = nboPackUShort(buf, uint16_t(skinIndex));
    ::memcpy(buf, name, ::strlen(name));
    buf = (void*)((char*)buf + CallSignLen);
    ::memcpy(buf, motto, ::strlen(motto));
    buf = (void*)((char*)buf + MottoLen);
    ::memcpy(buf, token, ::strlen(token));
    buf = (void*)((char*)buf + TokenLen);
    ::memcpy(buf, getAppVersion(), ::strlen(getAppVersion()) + 1);
    buf = (void*)((char*)buf + VersionLen);
    ::memcpy(buf, locale, ::strlen(locale));
    buf = (void*)((char*)buf + LocaleLen);
    send(MsgEnter, sizeof(msg), msg);
}

bool ServerLink::readEnter(std::string& reason,
                           uint16_t& code, uint16_t& rejcode)
{
    // wait for response
    uint16_t len;
    char msg[MaxPacketLen];

    while (true)
    {
        if (this->read(code, len, msg, -1) < 0)
        {
            reason = "Communication error joining game [No immediate respose].";
            return false;
        }

        if (code == MsgAccept)
            return true;
        else if (code == MsgSuperKill)
        {
            reason = "Server forced disconnection.";
            return false;
        }
        else if (code == MsgReject)
        {
            const void *buf;
            char buffer[MessageLen];
            buf = nboUnpackUShort(msg, rejcode); // filler for now
            buf = nboUnpackString(buf, buffer, MessageLen);
            buffer[MessageLen - 1] = '\0';
            reason = buffer;
            return false;
        }
        // ignore other codes so that bzadmin doesn't choke
        // on the MsgReceiveChat's that the server can send before
        // the MsgAccept (authorization holdoff, etc...)
    }

    return true;
}

void ServerLink::sendCaptureFlag(TeamColor team)
{
    char msg[2];
    nboPackUShort(msg, uint16_t(team));
    send(MsgCaptureFlag, sizeof(msg), msg);
}

void ServerLink::sendRequestFlag(int flagIndex)
{
    char msg[2];
    nboPackUShort(msg, uint16_t(flagIndex));
    send(MsgRequestFlag, sizeof(msg), msg);
}

void ServerLink::sendDropFlag(const float* position)
{
    char msg[12];
    void* buf = msg;
    buf = nboPackVector(buf, position);
    send(MsgDropFlag, sizeof(msg), msg);
}

void  ServerLink::sendKilled(const PlayerId& killer, int reason, int shotId, const FlagType::Ptr flagType, int phydrv)
{
    char msg[PlayerIdPLen + 2 + 2 + FlagPackSize + 4];
    void* buf = msg;

    buf = nboPackUByte(buf, killer);
    buf = nboPackUShort(buf, int16_t(reason));
    buf = nboPackUShort(buf, uint16_t(shotId));
    buf = flagType->pack(buf);

    if (reason == PhysicsDriverDeath)
        buf = nboPackInt(buf, phydrv);

    send(MsgKilled, (char*)buf - (char*)msg, msg);
}


void   ServerLink::sendPlayerUpdate(Player* player)
{
    char msg[PlayerUpdatePLenMax];
    // Send the time frozen at each start of scene iteration, as all
    // dead reckoning use that
    const float timeStamp = float(TimeKeeper::getTick() - TimeKeeper::getNullTime());
    void* buf = msg;
    uint16_t code;
    buf = nboPackFloat(buf, timeStamp);
    buf = nboPackUByte(buf, player->getId());

    // code will be MsgPlayerUpdate or MsgPlayerUpdateSmall
    buf = player->pack(buf, code);

    // variable length
    const int len = (char*)buf - (char*)msg;

    send(code, len, msg);
}

void  ServerLink::sendMsgFireShot(const FiringInfo& info)
{
    char msg[FiringInfoPLen];
    void* buf = msg;
    buf = info.pack(buf);
    send(MsgFireShot, sizeof(msg), msg);
}

void ServerLink::sendEndShot(const PlayerId& source,
                             int shotId, int reason)
{
    char msg[PlayerIdPLen + 4];
    void* buf = msg;
    buf = nboPackUByte(buf, source);
    buf = nboPackUShort(buf, uint16_t(shotId));
    buf = nboPackUShort(buf, uint16_t(reason));
    send(MsgShotEnd, sizeof(msg), msg);
}

void ServerLink::sendAlive()
{
    send(MsgAlive, 0, NULL);
}

void ServerLink::sendTeleport(int from, int to)
{
    char msg[4];
    void* buf = msg;
    buf = nboPackUShort(buf, uint16_t(from));
    buf = nboPackUShort(buf, uint16_t(to));
    send(MsgTeleport, sizeof(msg), msg);
}

void ServerLink::sendStealFlag(const PlayerId& from, const PlayerId& to)
{
    char msg[PlayerIdPLen * 2];
    void* buf = msg;
    buf = nboPackUByte(buf, from);
    buf = nboPackUByte(buf, to);
    send(MsgStealFlag, sizeof(msg), msg);
}

void ServerLink::sendNewRabbit()
{
    send(MsgNewRabbit, 0, NULL);
}

void ServerLink::sendPaused(bool paused)
{
    uint8_t p = paused;
    send(MsgPause, 1, &p);
}

void ServerLink::sendAutoPilot(bool autopilot)
{
    uint8_t p = autopilot;
    send(MsgAutoPilot, 1, &p);
}

void ServerLink::sendUDPlinkRequest()
{
    if ((server_abilities & CanDoUDP) != CanDoUDP)
        return; // server does not support udp (future list server test)

    char msg[1];
    unsigned short localPort;
    void* buf = msg;

    struct sockaddr_in serv_addr;

    if ((urecvfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        return; // we cannot comply
    }
#if 1
    AddrLen addr_len = sizeof(serv_addr);
    if (getsockname(fd, (struct sockaddr*)&serv_addr, (socklen_t*)&addr_len) < 0)
    {
        printError("Error: getsockname() failed, cannot get TCP port?");
        return;
    }
    if (bind(urecvfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) != 0)
    {
        printError("Error: getsockname() failed, cannot get TCP port?");
        return;  // we cannot get udp connection, bail out
    }

#else
    // TODO if nobody complains kill this old port 17200 code
    for (int port = 17200; port < 65000; port++)
    {
        ::memset((unsigned char *)&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(port);
        if (bind(urecvfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == 0)
            break;
    }
#endif
    localPort = ntohs(serv_addr.sin_port);
    memcpy((char *)&urecvaddr, (char *)&serv_addr, sizeof(serv_addr));

    if (debugLevel >= 1)
    {
        std::vector<std::string> args;
        char lps[10];
        sprintf(lps, "%d", localPort);
        args.push_back(lps);
        printError("Network: Created local UDP downlink port {1}", &args);
    }

    buf = nboPackUByte(buf, id);

    if (BzfNetwork::setNonBlocking(urecvfd) < 0)
        printError("Error: Unable to set NonBlocking for UDP receive socket");

    send(MsgUDPLinkRequest, sizeof(msg), msg);
}

// heard back from server that we can send udp
void ServerLink::enableOutboundUDP()
{
    ulinkup = true;
    if (debugLevel >= 1)
        printError("Server got our UDP, using UDP to server");
}
// confirm that server can send us UDP
void ServerLink::confirmIncomingUDP()
{
    // This is really a hack. enableOutboundUDP will be setting this
    // but frequently the udp handshake will finish first so might as
    // well start with udp as soon as we can
    ulinkup = true;

    if (debugLevel >= 1)
        printError("Got server's UDP packet back, server using UDP");
    send(MsgUDPLinkEstablished, 0, NULL);
}

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
